﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using CI.Interfaces;
using System.Diagnostics;
using CI.Organism;
using CI.Utils;
using CI.WebService;

namespace CI.Mesopotamia
{
    /// <summary>
    /// Scenario's
    /// </summary>
    [DataContract]
    [KnownType("GetKnownTypes")]
    public class Scenario : IScenario
    {

        #region Properties
        /// <summary>
        /// Scene name
        /// Used in DAL name used to load and save
        /// </summary> 
        [DataMember]
        public string ScenarioName { get; set; }

        /// <summary>
        /// Manifest file
        /// </summary>
        [DataMember]
        public string ManifestFile { get; set; }

        /// <summary>
        /// Version of Hosting Environment
        /// </summary>
        public string HostingEnvironmentVersionInformation
        {
            get
            {
                return _hostingEnvironmentversionInformation;
            }
        }
        [DataMember]
        private string _hostingEnvironmentversionInformation = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();

        /// <summary>
        /// max number to load from the dal for the scenario
        /// </summary>
        [DataMember]
        public int MaxLoadCount { get; set; }

        /// <summary>
        /// max number to save to the dal for the scenario
        /// </summary>
        [DataMember]
        public int MaxSaveCount { get; set; }

        /// <summary>
        /// Scene Description
        /// </summary>
        [DataMember]
        public string ScenarioDescription { get; set; }

        #region Boundrys
        /// <summary>Max X radius boundry</summary>
        [DataMember]
        public float BoundryXRadiusMax
        {
            get;
            set;
        }

        /// <summary>Max Y radius boundry</summary>
        [DataMember]
        public float BoundryYRadiusMax
        {
            get;
            set;
        }

        /// <summary>Max Z radius boundry</summary>
        [DataMember]
        public float BoundryZRadiusMax
        {
            get;
            set;
        }
        #endregion

        /// <summary>Fitness function for the environment</summary>
        [DataMember]
        public string FitnessFunction { get; set; }

        /// <summary>Simulation environment</summary>
        [DataMember]
        public string SimulationRestrictions { get; set; }

        /// <summary>
        /// List of required entitys
        /// </summary>
        [DataMember]
        public List<IScenarioEntityRequirements> RequiredOrganisms
        {
            get { return _requiredOrganisms; }
            set { _requiredOrganisms = value; }
        }
        private List<IScenarioEntityRequirements> _requiredOrganisms = new List<IScenarioEntityRequirements>();

        /// <summary>
        /// Fall back list of Enitys in the scenario eg, Amoeba, iRobot in case the resources in the resource file arent enough
        /// </summary>
        [DataMember]
        public List<IOrganism> FallBackOrganisms
        {
            get { return _fallBackOrganisms; }
            set { _fallBackOrganisms = value; }
        }
        private List<IOrganism> _fallBackOrganisms = new List<IOrganism>();

        /// <summary>
        /// Entity organisms loaded through DAL
        /// </summary>
        public IDAL ExternalOrganisms { get; set; }

        #endregion

        /// <summary>
        /// Sim Environment
        /// </summary>
        protected readonly SimEnv SimEnv = SimEnv.Instance;

        /// <summary>
        /// get the known types
        /// </summary>
        /// <returns></returns>
        public static Type[] GetKnownTypes()
        {
            // else create a list
            var types = new List<Type>
                            {
                                typeof (ScenarioLocalXML),
                                typeof (ScenarioGrid),
                                typeof (Scenario),
                                typeof (ScenarioEntityRequirements),
                                typeof (Scenario),
                                typeof (MSRBHost),
                                typeof (Organism.Organism),
                                typeof (OrganismCollection),
                                typeof (GeneCollection),
                                typeof (RequirementCollection),
                                typeof (DNA),
                                typeof (SimpleNeuralSystem),
                                typeof (CellCollection),
                                typeof (Location),
                                typeof (Cell),
                                typeof (ChromosomeCollection),
                                typeof (NeuronCollection),
                                typeof (SynapseCollection),
                                typeof (StepNeuron),
                                typeof (HBOffsetTime),
                                typeof (HBOffsetTimeCell),
                                typeof (IOSynapseValue),
                                typeof (iRobot),
                                typeof (Amoeba),
                                typeof (StepNeuron),
                                typeof (GeneReproductionAsexual),
                                typeof (GeneCellular),
                                typeof (Chromosome),
                                typeof (UseItOrLoseIt),
                                typeof (SimpleSynapse),
                                typeof (DAL.DALList),
                                typeof (SynapseIO),
                                typeof(VisualSynapseIO)
                            };

            return types.ToArray();
        }

        /// <summary>
        /// Copy scenario values from one to another
        /// </summary>
        /// <param name="source">source scenario</param>
        /// <param name="destination">destination scenario</param>
        protected void CopyScenario(IScenario source, IScenario destination)
        {
            SimEnv.TraceLogger.LogDebug("CopyScenario - start");
            try
            {
                destination.BoundryXRadiusMax = source.BoundryXRadiusMax;
                destination.BoundryYRadiusMax = source.BoundryYRadiusMax;
                destination.BoundryZRadiusMax = source.BoundryZRadiusMax;
                destination.ExternalOrganisms = source.ExternalOrganisms;
                destination.FallBackOrganisms = source.FallBackOrganisms;
                if (source.FitnessFunction != null)
                    destination.FitnessFunction = source.FitnessFunction.Trim();
                destination.ManifestFile = source.ManifestFile;
                destination.RequiredOrganisms = source.RequiredOrganisms;
                destination.ScenarioDescription = source.ScenarioDescription;
                destination.ScenarioName = source.ScenarioName;
                if (source.SimulationRestrictions != null)
                    destination.SimulationRestrictions = source.SimulationRestrictions.Trim();
                destination.MaxLoadCount = source.MaxLoadCount;
                destination.MaxSaveCount = source.MaxSaveCount;
            }
            catch (Exception ex)
            {
                SimEnv.TraceLogger.LogException("Exception copying scenario, setting destination to null", ex);
                destination = null;
            }
            SimEnv.TraceLogger.LogDebug("CopyScenario - End");
        }

        /// <summary>
        /// Compare what we loaded in with the DAL compared to what the 
        /// scenario requirements are in the requirements
        /// 
        /// Set all the organisms at their location specfied in the requirements
        /// </summary>
        public SynchronizedCollection<IOrganism> CompareEnvironmentUpdateRequirements(IOrganismCollection dalLoadedOrganisms, IHostingEnvironment hostingEnvironment)
        {
            #region Argument Checks
            if (dalLoadedOrganisms == null)
                throw new ArgumentException("organisms");
            if (hostingEnvironment == null)
                throw new ArgumentException("hostingEnvironment");
            #endregion


            SimEnv.TraceLogger.LogDebug("CompareEnvironmentUpdateRequirements - Starting");

            // new alive list
            var newAliveList = new SynchronizedCollection<IOrganism>();

            // go through all the requirements
            foreach (IScenarioEntityRequirements requirement in RequiredOrganisms)
            {

                SimEnv.TraceLogger.LogDebug("Required Type : " + requirement.OrganismType + " Count : " + requirement.Locations.Count);

                // starting list
                var trimmedList = new List<IOrganism>();
                var fittestList = new List<IOrganism>();

                SimEnv.TraceLogger.LogDebug("Use fallback only : " + requirement.UseFallBackOnly);
                SimEnv.TraceLogger.LogDebug("Scenario Needed : " + requirement.Locations.Count);

                if (!requirement.UseFallBackOnly)
                {
                    SimEnv.TraceLogger.LogDebug("Adding dal organisms");
                    AddOrganismsIfTheSameType(requirement, dalLoadedOrganisms.Alive, fittestList);
                    SimEnv.TraceLogger.LogDebug("Dal total : " + fittestList.Count());
                }

                // if their is not enough loaded from the DAL use the FALLBACK LIST
                if (fittestList.Count < requirement.Locations.Count)
                {
                    SimEnv.TraceLogger.LogDebug("Adding fallback organisms");
                    AddOrganismsIfTheSameType(requirement, FallBackOrganisms, fittestList);
                    SimEnv.TraceLogger.LogDebug("Dal total : " + fittestList.Count());
                }

                CheckForEnoughOrganisms(requirement, fittestList);
                TrimIfToManyOrganisms(requirement, trimmedList, fittestList);
                SetOrganismLocations(newAliveList, requirement, trimmedList);
            }

            OutputList(newAliveList);

            return newAliveList;
        }

        [Conditional("DEBUG")]
        private void OutputList(IEnumerable<IOrganism> newAliveList)
        {
            SimEnv.TraceLogger.LogDebug("Final Dal list");

            // dump final list
            foreach (IOrganism organism in newAliveList)
            {
                SimEnv.TraceLogger.LogDebug(organism + ": " + organism.Guid);
            }

            SimEnv.TraceLogger.LogDebug("CompareEnvironmentUpdateRequirements - Finishing");
        }

        private static void SetOrganismLocations(SynchronizedCollection<IOrganism> newAliveList, IScenarioEntityRequirements requirement, IList<IOrganism> trimList)
        {
            // now add it to the final list in the location set
            for (int i = 0; i < trimList.Count; i++)
            {
                // set its location
                trimList[i].Location = requirement.Locations[i];

                // add it
                newAliveList.Add(trimList[i]);

                // reset its fitness
                trimList[i].FitnessValue = 0;
            }
        }

        private static void TrimIfToManyOrganisms(IScenarioEntityRequirements requirement, ICollection<IOrganism> trimList, List<IOrganism> fittestList)
        {
            // trim it to the size we want, randomly grab ones in case of inbreeding on a mass scale
            do
            {
                // get a organism
                var foundOranism = fittestList[Mutation.CollectionSelect(fittestList)];
                // if not already in the list add it
                if (!trimList.Contains(foundOranism))
                    trimList.Add(foundOranism);
            }
            while (trimList.Count < requirement.Locations.Count);
        }

        private static void CheckForEnoughOrganisms(IScenarioEntityRequirements requirement, ICollection fittestList)
        {
            // check and see if we have enough now
            if (fittestList.Count < requirement.Locations.Count)
            {
                throw new Exception("Scenario DAL and Fallback organism collection does not have enough to meet the requirments");
            }
        }

        private void AddOrganismsIfTheSameType(IScenarioEntityRequirements requirement, IEnumerable<IOrganism> checkList, ICollection<IOrganism> outputList)
        {
            var requirementType = requirement.OrganismType.GetType();

            foreach (IOrganism organism in checkList)
            {
                // if its the same type
                if (requirementType.Equals(organism.Zygote().GetType()))
                {
                    SimEnv.TraceLogger.LogDebug(string.Format("Adding Organism {0} with GUID {1}", organism, organism.Guid));

                    IOrganism foundOrganism = FindOrganismInFittestList(outputList, organism);

                    if (foundOrganism != null)
                    {
                        SimEnv.TraceLogger.LogWarn("Duplicate Organisms found when seraching to add to Fittest list - CompareEnvironmentUpdateRequirements");
                        continue;
                    }

                    InitOrganismWithinEnviroment(organism);
                    SimEnv.TraceLogger.LogDebug(string.Format("Added Organism {0} with GUID {1}", organism, organism.Guid));

                    // add it to the list
                    outputList.Add(organism);
                }
            }

        }

        private void InitOrganismWithinEnviroment(IOrganism organism)
        {
            // set the organism to be this hosting environment
            organism.HostingEnvironment = SimEnv.HostEnv;
            // set the correct collection
            organism.Organisms = SimEnv.HostEnv.Organisms;
            // give it some energy and init CIET
            organism.Energy = organism.InitEnergy;
            organism.CIET = organism.InitCIET;
        }

        private static IOrganism FindOrganismInFittestList(IEnumerable<IOrganism> fittestList, IOrganism organism)
        {
            return fittestList.FirstOrDefault(organismFind => organismFind.Guid == organism.Guid);
        }

        public virtual ScenarioOrganismCollection GetScenarioOrganisms(string scenarioName, int startIndex, int endIndex)
        {
            throw new NotImplementedException();
        }


        /// <summary>
        /// see if all the values in the objects(not the objects) are equal
        /// selective  comparisions
        /// </summary>
        /// <param name="scenario">scenario to comare against</param>
        /// <returns>true if the same values</returns>
        public bool EqualsValueTypes(IScenario scenario)
        {
            // make sure we got one to compare against
            if (scenario == null)
                return false;

            // compare the max scenario radius
            #region  compare the radius
            if (BoundryXRadiusMax != scenario.BoundryXRadiusMax)
                return false;

            if (BoundryYRadiusMax != scenario.BoundryYRadiusMax)
                return false;

            if (BoundryZRadiusMax != scenario.BoundryZRadiusMax)
                return false;
            #endregion

            // compare the load and save counts
            if (MaxLoadCount != scenario.MaxLoadCount)
                return false;
            if (MaxSaveCount != scenario.MaxSaveCount)
                return false;

            // compare the strings
            if (ManifestFile != scenario.ManifestFile)
                return false;
            if (ScenarioDescription != scenario.ScenarioDescription)
                return false;
            if (ScenarioName != scenario.ScenarioName)
                return false;

            // compare the types
            if (!(FitnessFunction.GetType().Equals(scenario.FitnessFunction.GetType())))
                return false;
            if (!(SimulationRestrictions.GetType().Equals(scenario.SimulationRestrictions.GetType())))
                return false;

            // compare the requirements count
            if (RequiredOrganisms.Count != scenario.RequiredOrganisms.Count)
                return false;

            // check the requirements are equal
            if (!EqualsRequirements(scenario))
                return false;

            // compare the fallback organism count
            if (FallBackOrganisms.Count != scenario.FallBackOrganisms.Count)
                return false;

            // check the requirements are equal
            if (!EqualsFallBackOrganisms(scenario))
                return false;

            return true;
        }

        /// <summary>
        /// compare the requirements in the scenario
        /// selective  comparisions
        /// </summary>
        /// <param name="scenario"></param>
        private bool EqualsRequirements(IScenario scenario)
        {
            // check each of the requirments
            foreach (ScenarioEntityRequirements requirement in RequiredOrganisms)
            {
                IScenarioEntityRequirements equalRequirment = null;

                // get the same type
                foreach (ScenarioEntityRequirements compRequirement in scenario.RequiredOrganisms)
                {
                    if (compRequirement.OrganismType.GetType() == requirement.OrganismType.GetType())
                    {
                        equalRequirment = compRequirement;
                        break;
                    }
                }

                // if not found return false
                if (equalRequirment == null)
                    return false;

                // compare the fallback
                if (requirement.UseFallBackOnly != equalRequirment.UseFallBackOnly)
                    return false;

                // compare the locations count
                if (requirement.Locations.Count != equalRequirment.Locations.Count)
                    return false;

                // compare the locations, make sure we find a match
                foreach (ILocation location in requirement.Locations)
                {
                    // did we find a match
                    bool found = false;

                    // check each of them
                    foreach (ILocation compLocation in equalRequirment.Locations)
                    {
                        if (location.XAxis != compLocation.XAxis)
                            continue;
                        if (location.YAxis != compLocation.YAxis)
                            continue;
                        if (location.ZAxis != compLocation.ZAxis)
                            continue;
                        if (location.XRadius != compLocation.XRadius)
                            continue;
                        if (location.YRadius != compLocation.YRadius)
                            continue;
                        if (location.ZRadius != compLocation.ZRadius)
                            continue;
                        // else we got a match so save as true
                        found = true;
                        break;
                    }

                    // if none found
                    if (!found)
                        return false;
                }
            }

            return true;
        }

        /// <summary>
        /// compare the fallback organisms in the scenaio
        /// selective  comparisions
        /// </summary>
        /// <param name="scenario"></param>
        /// <returns></returns>
        private bool EqualsFallBackOrganisms(IScenario scenario)
        {
            // check each of the organisms
            foreach (IOrganism organism in FallBackOrganisms)
            {
                // todo : start by matching the GUID's
                IOrganism foundOrganism = null;
                foreach (IOrganism searchOrganism in scenario.FallBackOrganisms)
                {
                    if (organism.Guid.Equals(searchOrganism.Guid))
                    {
                        foundOrganism = searchOrganism;
                        break;
                    }
                }

                // if we didnt find anything then exit
                if (foundOrganism == null)
                    return false;

                if (!organism.EqualsValueTypes(foundOrganism))
                    return false;
            }

            return true;

        }

        public virtual void Load()
        {
            throw new NotImplementedException();
        }
        public virtual void Save()
        {
            throw new NotImplementedException();
        }
        public virtual IEnumerable<string> GetScenariosAvailable()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Scenario Entity Requirements
        /// </summary>
        [DataContract]
        public class ScenarioEntityRequirements : IScenarioEntityRequirements
        {
            /// <summary>
            /// Type of organisms we want
            /// </summary>
            [DataMember]
            public IPhysical OrganismType { get; set; }

            /// <summary>
            /// List of locations we want them at
            /// </summary>
            [DataMember]
            public List<ILocation> Locations
            {
                get { return _locations; }
                set { _locations = value; }
            }
            private List<ILocation> _locations = new List<ILocation>();

            /// <summary>
            /// Use the fall back organisms only, this will inhibit use of the load org's
            /// </summary>
            [DataMember]
            public bool UseFallBackOnly { get; set; }
        }

    }
}
